#include <iostream>
#include <sstream>
#include "PartidaServicio.h"

using namespace std;
using namespace TP::Servicios;

#define PARTIDA_EXISTENTE   1
#define JUGADOR_JUGANDO     2
#define JUGADOR_OCIOSO      3
#define MUNDO_INEXISTENTE   4
#define NIVEL_INEXISTENTE   5
#define PARTIDA_INEXISTENTE   6
#define AVATAR_NO_DISPONIBLE 7

PartidaServicio::PartidaServicio() { }

PartidaServicio::~PartidaServicio() { }

void PartidaServicio::iniciar() { }

void PartidaServicio::finalizar() { }

void PartidaServicio::mensajeEntranteManejador(IMensajero* pMensajero,
		const string& pMensaje) {
	//@todo: Implementar el manejador de mensajes entrantes del servicio Mundo.
	try {
		vector<string> comando;
		this->hidratarMensaje(pMensaje, comando);

		if (PARTIDA == comando[0]) {
			if (LISTAR_PARTIDAS == comando [1])
				this->__listarPartidas(pMensajero);

			if (UNIR_PARTIDA == comando[1])
				this->__unirAPartida(pMensajero, comando);

			if (CREAR_PARTIDA == comando [1])
				this->__crearPartida(pMensajero, comando);

			if (ABANDONAR_PARTIDA == comando[1])
				this->__abandonarPartida(pMensajero, comando);

			if (INGRESAR_A_PARTIDA == comando[1])
				this->__unirAPartida(pMensajero, comando);
		}

	}
	catch (Excepcion& pEx) {
		cerr << pEx.what() << endl;
	}
}

void PartidaServicio::__listarPartidas(IMensajero* pMensajero) {

	_mutex.lock();

	vector<string> lista = this->_motorJuego->listarPartidas();

	if (lista.size() > 0) {
		std::stringstream respuesta;
		respuesta << "";

		for (vector<string>::iterator it = lista.begin(); it != lista.end();
				++it)
			respuesta << (*it) << endl;

		this->notificar(pMensajero, respuesta.str());
	}
	else
		this->notificar(pMensajero, NULO);

	_mutex.unlock();
}

void PartidaServicio::__unirAPartida(IMensajero* pMensajero,
		vector<string>& pComando) {
	// formato de comando: Partida.ingresarAPartida (Jugador, pNombrePartida)
	_mutex.lock();
	Jugador* jugador = NULL;
	Mundo* mundo = NULL;
	Nivel* nivel = NULL;
	try {
		// Valido que el jugador no se encuentre jugando una partida actualmente
		jugador = Jugador::crearDeXML(pComando[2]);

		if (NULL != this->_motorJuego->getPartidaPorJugador(jugador))
			throw JUGADOR_JUGANDO;

		// Valido que exista una partida corriendo con el nombre especificado.
		if (!this->partidaExistente(pComando[3]))
			throw PARTIDA_INEXISTENTE;

		if (!this->_motorJuego->cerditoAvatarDisponibles(pComando[3]))
			throw AVATAR_NO_DISPONIBLE;

		// ----------------------------------------
		// Se encuentra en condiciones de generar una partida nueva.
		// ----------------------------------------

		/* Agrego al jugador a la partida de juego.
		 */
		this->_motorJuego->asignarJugadorACerditoAvatar(pComando[3], jugador);

		// Notifico la respuesta al cliente.
		this->notificar(pMensajero,
				this->_motorJuego->getPartidaPorJugadorAString(jugador));
	}
	catch (int pEx) {
		switch (pEx) {
			case JUGADOR_JUGANDO:
				this->notificarError(pMensajero,
						"El jugador se encuentra jugnado una partida actualmente.");
				break;

			case PARTIDA_INEXISTENTE:
				this->notificarError(pMensajero,
						"La partida indicada no existe.");
				break;

			case AVATAR_NO_DISPONIBLE:
				this->notificarError(pMensajero, "El nivel se encuentra con la"
						" capacidad de jugadores completa.");
				break;
		}
	}
	catch (Excepcion& pEx) {
		this->notificarError(pMensajero, pEx.what());
	}
	_mutex.unlock();
}

void PartidaServicio::__crearPartida(IMensajero* pMensajero,
		vector<string>& pComando) {
	_mutex.lock();
	Jugador* jugador = NULL;
	Mundo* mundo = NULL;
	Nivel* nivel = NULL;
	try {
		// formato de comando: {0:Partida, 1:CrearPartida, 2:Jugador,
		// 3:nombreMundo, 4:idNivel, 5:pNombrePartida}

		// Valido que el jugador no se encuentre jugando una partida actualmente
		jugador = Jugador::crearDeXML(pComando[2]);
		if (NULL != this->_motorJuego->getPartidaPorJugador(jugador))
			throw JUGADOR_JUGANDO;

		// Valido que no exista una partida corriendo con el nombre especificado
		if (this->partidaExistente(pComando[5]))
			throw PARTIDA_EXISTENTE;

		// Valido que el mundo indicado exista.
		mundo = this->_mundoSrv->getMundoPorNombre(pComando[3]);

		if (NULL == mundo)
			throw MUNDO_INEXISTENTE;

		// Valido que el nivel indicado exista.
		std::stringstream strstream(pComando[4]);
		unsigned int idNivel = 0;
		strstream >> idNivel;
		nivel = mundo->getNivel(idNivel);
		if (NULL == nivel)
			throw NIVEL_INEXISTENTE;

		// ----------------------------------------
		// Se encuentra en condiciones de generar una partida nueva.
		// ----------------------------------------

		/* Instancio la partida y la ejecuto en el motor de juego.
		 * El jugador especificado seria el administrador de la partida.
		 */
		this->_motorJuego->correrPartida(new Partida(pComando[5],
				nivel->getEscenario(), jugador));

		// Notifico la respuesta al cliente.
		this->notificar(pMensajero,
				this->_motorJuego->getPartidaPorJugadorAString(jugador));
	}
	catch (int pEx) {
		switch (pEx) {
			case PARTIDA_EXISTENTE:
				this->notificarError(pMensajero, "Ya existe una partida que"
						" posee el nombre especificado.");
				break;

			case JUGADOR_JUGANDO:
				this->notificarError(pMensajero, "El jugador se encuentra "
						"jugnado una partida actualmente.");
				break;

			case MUNDO_INEXISTENTE:
				this->notificarError(pMensajero, "El mundo especificado "
						"no existe.");
				break;

			case NIVEL_INEXISTENTE:
				this->notificarError(pMensajero, "El nivel especificado "
						"no existe.");
				break;
		}
	}
	catch (Excepcion& pEx) {
		this->notificarError(pMensajero, pEx.what());
	}
	_mutex.unlock();
}

bool PartidaServicio::partidaExistente(const string& pNombrePartida) {
	return NULL != this->_motorJuego->getPartidaPorNombre(pNombrePartida);
}

MotorJuego* PartidaServicio::setMotorJuego(MotorJuego* pMotor) {
	this->_motorJuego = pMotor;
	return pMotor;
}

MundoServicio* PartidaServicio::setMundoServicio(MundoServicio* pServicio) {
	this->_mundoSrv = pServicio;
	return pServicio;
}

void PartidaServicio::__abandonarPartida(IMensajero* pMensajero,
		vector<string>& pComando) {
	// formato de comando: Partida.abandonarPartida (Jugador)
	Jugador* jugador = NULL;
	try {
		_mutex.lock();
		// Valido que el jugador se encuentre jugando una partida actualmente.
		jugador = Jugador::crearDeXML(pComando[2]);
		Partida* partida = this->_motorJuego->getPartidaPorJugador(jugador);
		if (NULL == partida)
			throw JUGADOR_OCIOSO;

		if (*(partida->getJugadorAdministrador()) == *jugador) {
			this->_motorJuego->finalizarPartida(partida);
			this->notificar(pMensajero, "La partida ha sido finalizada.");
		}
		else {
			this->_motorJuego->removerJugadorDePartida(partida, jugador);
			this->notificar(pMensajero, "El jugador ha habandonado la partida"
					" de juego.");
		}
	}
	catch (int pEx) {
		switch (pEx) {
			case JUGADOR_OCIOSO:
				this->notificarError(pMensajero, "El jugador no se encuentra"
						" jugando partida alguna.");
				break;
		}
	}
	catch (Excepcion& pEx) {
		this->notificarError(pMensajero, pEx.what());
	}
	_mutex.unlock();
}